#include "demo.h"
#include "mbutils.h"

/* --------------------------------------- 宏定义 ------------------------------------------*/

#define SLAVE_ID            0x01            /* 从站ID */
#define MB_BAUDRATE         115200          /* Modbus波特率 */
#define MB_PARITY           MB_PAR_NONE     /* 校验位 */
#define MB_STOPBITS         UART_STOPBITS_1 /* 停止位 */

/* 输入寄存器相关宏定义 */
#define REG_INPUT_START     0x0001          /* 输入寄存器起始地址 */
#define REG_INPUT_NREGS     10              /* 输入寄存器数量 */

/* 保持寄存器相关宏定义 */
#define REG_HOLDING_START   0x0001          /* 保持寄存器起始地址 */
#define REG_HOLDING_NREGS   10              /* 保持寄存器数量 */

/* 线圈寄存器相关宏定义 */
#define REG_COILS_START     0x0001          /* 线圈寄存器起始地址 */
#define REG_COILS_NBITS     16              /* 线圈数量（单位：位/bit） */
#define COILS_NBYTES        ((REG_COILS_NBITS + 7) / 8)  /* 计算需要的字节数：向上取整到字节 */
#define COILS_BUFSIZE       (((COILS_NBYTES + 1) & ~1))  /* 偶数字节对齐：满足xMBUtil双字节访问要求 */

/* 离散输入寄存器相关宏定义 */
#define REG_DISCRETE_START  0x0001          /* 离散输入寄存器起始地址 */
#define REG_DISCRETE_NBITS  16              /* 离散输入数量（单位：位/bit） */
#define DISCRETE_NBYTES     ((REG_DISCRETE_NBITS + 7) / 8)  /* 计算需要的字节数：向上取整到字节 */
#define DISCRETE_BUFSIZE    (((DISCRETE_NBYTES + 1) & ~1))  /* 偶数字节对齐：满足xMBUtil双字节访问要求 */

/* ------------------------------------- 静态变量 ------------------------------------------ */

/* 输入寄存器相关 */
static USHORT usRegInputStart = REG_INPUT_START;        /* 输入寄存器起始地址 */
static USHORT usRegInputBuf[REG_INPUT_NREGS];           /* 输入寄存器缓冲区 */

/* 保持寄存器相关 */
static USHORT usRegHoldingStart = REG_HOLDING_START;    /* 保持寄存器起始地址 */
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];       /* 保持寄存器缓冲区 */

/* 线圈寄存器相关 */
static UCHAR ucRegCoilsStart = REG_COILS_START;         /* 线圈寄存器起始地址 */
static UCHAR ucRegCoilsBuf[COILS_BUFSIZE];              /* 线圈寄存器缓冲区 */

/* 离散输入寄存器相关 */
static UCHAR ucRegDiscreteStart = REG_DISCRETE_START;   /* 离散输入寄存器起始地址 */
static UCHAR ucRegDiscreteBuf[DISCRETE_BUFSIZE];        /* 离散输入寄存器缓冲区 */

static uint32_t lastToggleTime;                         /* 上次LED切换时间 */

/* ------------------------------------- 前向声明------------------------------------------- */

void Error_Handler(void); /* 标准定义在main.h中 */

/* ------------------------------- Modbus寄存器回调函数 ------------------------------------- */

eMBErrorCode eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs);                          /* 输入寄存器回调函数 */
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode); /* 保持寄存器回调函数 */
eMBErrorCode eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode);  /* 线圈寄存器回调函数 */
eMBErrorCode eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNDiscrete);                   /* 离散输入寄存器回调函数 */

/* ------------------------------------ 公共API函数 ----------------------------------------- */
/**
 * @brief  初始化Modbus功能
 * @note   在系统初始化期间调用一次
 * @retval 无
 */
void modbusInit(void)
{
    eMBErrorCode eStatus; /* 错误代码 */
    
    /* 初始化LED0引脚 */
    LED0_GPIO_Init();

    /* 初始化串口 */
    MB_Uart_Init();

    /* 初始化Modbus协议栈 */
    eMBInit(MB_RTU, SLAVE_ID, 0, MB_BAUDRATE, MB_PARITY, MB_STOPBITS);

    /* 初始化输入寄存器值 */
    usRegInputBuf[0] = 0; /* 初始化计数器 */
    usRegInputBuf[1] = 0x12;
    usRegInputBuf[2] = 0x34;
    usRegInputBuf[3] = 0x56;
    usRegInputBuf[4] = 0x78;
    usRegInputBuf[5] = 0x90;
    usRegInputBuf[6] = 0xAB;
    usRegInputBuf[7] = 0xCD;
    usRegInputBuf[8] = 0xEF;
    usRegInputBuf[9] = 0x12;

    /* 初始化保持寄存器值 */
    usRegHoldingBuf[0] = 0x1000;
    usRegHoldingBuf[1] = 0x2000;
    usRegHoldingBuf[2] = 0x3000;
    usRegHoldingBuf[3] = 0x4000;
    usRegHoldingBuf[4] = 0x5000;
    usRegHoldingBuf[5] = 0x6000;
    usRegHoldingBuf[6] = 0x7000;
    usRegHoldingBuf[7] = 0x8000;
    usRegHoldingBuf[8] = 0x9000;
    usRegHoldingBuf[9] = 0xA000;

    /* 初始化线圈寄存器值（按位存储，每字节8个线圈） */
    ucRegCoilsBuf[0] = 0x11;  /* 线圈0-7: 00010001 (线圈0=ON, 线圈4=ON, 其他=OFF) */
    ucRegCoilsBuf[1] = 0x22;  /* 线圈8-15: 00100010 (线圈9=ON, 线圈13=ON, 其他=OFF) */

    /* 初始化离散输入寄存器值（按位存储，每字节8个离散输入） */
    ucRegDiscreteBuf[0] = 0x33;  /* 离散输入0-7: 00110011 (输入0=ON, 输入1=ON, 输入4=ON, 输入5=ON, 其他=OFF) */
    ucRegDiscreteBuf[1] = 0x44;  /* 离散输入8-15: 01000100 (输入10=ON, 输入14=ON, 其他=OFF) */

    /* 启用Modbus协议栈 */
    eStatus = eMBEnable();

    if( eStatus != MB_ENOERR )
    {
        Error_Handler();
    }

    lastToggleTime = HAL_GetTick();
}

/**
 * @brief  轮询Modbus协议栈
 * @note   在主循环中定期调用
 * @retval 无
 */
void modbusPoll(void)
{
    /* 处理Modbus事件 */
    (void)eMBPoll();

    /* 更新第一个寄存器作为计数器 */
    usRegInputBuf[0]++;

    /* 每500毫秒反转LED以提供视觉反馈 */
    if (HAL_GetTick() - lastToggleTime >= 500)
    {
#ifndef MB_TIMER_DEBUG
        LED0_TOGGLE();  /* 反转LED0 */
#endif
        lastToggleTime = HAL_GetTick();
    }
}

/* --------------------------------------- Modbus寄存器回调函数 ------------------------------*/

/**
 * @brief 输入寄存器读取回调函数（只读，功能码04）
 * @details 输入寄存器为16位只读寄存器，通常用于对外提供传感器读数、
 *          状态量等不会被主站写入修改的数据。本函数需将内部缓冲
 *          `usRegInputBuf[]` 中的数据按大端字节序（高字节在前、低字节在后）
 *          拆分写入 `pucRegBuffer` 返回给主站。
 *
 * 对应功能码：04 读输入寄存器 eMBFuncReadInputRegister
 * 帧格式（主站→从站）：
 *   SlaveAddr(1) + FuncCode(1) + StartAddrHi(1) + StartAddrLo(1)
 *   + QuantityHi(1) + QuantityLo(1) + CRCLo(1) + CRCHi(1)
 * 帧格式（从站→主站）：
 *   SlaveAddr(1) + FuncCode(1) + ByteCount(1) + Data(2*N) + CRC(2)
 *
 * @param pucRegBuffer 输出缓冲区指针（协议栈提供）。应将读取到的寄存器值
 *                     以字节流形式依次写入：每个寄存器占2字节，先高后低。
 * @param usAddress    主站请求的起始寄存器地址（从站视角的寄存器地址）。
 * @param usNRegs      主站请求读取的寄存器数量（单位：16位寄存器）。
 * @return eMBErrorCode
 *         - MB_ENOERR: 成功，数据已按要求填充进 `pucRegBuffer`
 *         - MB_ENOREG: 地址越界或数量非法（协议栈将返回 ILLEGAL DATA ADDRESS）
 *
 * @note  地址合法性的判断通常为：区间 [usAddress, usAddress + usNRegs)
 *        必须完全处于本从站输入寄存器有效区间 [REG_INPUT_START,
 *        REG_INPUT_START + REG_INPUT_NREGS) 内。
 */
eMBErrorCode
eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{
    eMBErrorCode eStatus = MB_ENOERR;
    int iRegIndex;

    /* 检查地址范围是否有效 */
    if ((usAddress >= REG_INPUT_START) && (usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS))
    {
        iRegIndex = (int)(usAddress - usRegInputStart); /* 计算寄存器索引 */
        
        /* 将16位寄存器数据拆分为两个8位字节写入缓冲区（高字节在前） */
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] >> 8);   /* 提取高字节 */
            *pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] & 0xFF); /* 提取低字节 */
            iRegIndex++;    /* 下一个寄存器 */
            usNRegs--;      /* 剩余数量减一 */
        }
    }
    else
    {
        eStatus = MB_ENOREG; /* 寄存器地址超出范围 */
    }

    return eStatus;
}

/**
 * @brief 保持寄存器回调函数（可读写，功能码03/06/16/23）
 * @details 保持寄存器为16位可读写寄存器，常用于参数设定、配置信息等。
 *          - 03 读保持寄存器 eMBFuncReadHoldingRegister
 *          - 06 写保持寄存器 eMBFuncWriteHoldingRegister
 *          - 16 写多个保持寄存器 eMBFuncWriteMultipleHoldingRegister
 *          - 23 读写多个保持寄存器 eMBFuncReadWriteMultipleHoldingRegister
 *          读时将内部 `usRegHoldingBuf[]` 的值按大端顺序返回；写时将输入字节流
 *          合成为16位并写入对应位置。
 *
 * @param pucRegBuffer 数据缓冲区指针：
 *        - eMode == MB_REG_READ：作为输出缓冲区，每个寄存器写入2字节（高在前）。
 *        - eMode == MB_REG_WRITE：作为输入缓冲区，每2字节合成为一个寄存器值。
 * @param usAddress    起始保持寄存器地址。
 * @param usNRegs      操作的寄存器数量（单位：16位寄存器）。
 * @param eMode        访问模式：MB_REG_READ（读）或 MB_REG_WRITE（写）。
 * @return eMBErrorCode
 *         - MB_ENOERR: 成功
 *         - MB_ENOREG: 地址越界或数量非法
 */
eMBErrorCode
eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode)
{
    eMBErrorCode eStatus = MB_ENOERR;
    int iRegIndex;

    /* 检查地址范围是否有效 */
    if ((usAddress >= REG_HOLDING_START) && (usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS))
    {
        iRegIndex = (int)(usAddress - usRegHoldingStart);    /* 计算寄存器索引 */
        
        switch (eMode)
        {
            case MB_REG_READ:
                /* 读取保持寄存器：将16位寄存器数据拆分为两个8位字节写入缓冲区 */
                while (usNRegs > 0)
                {
                    *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] >> 8);     /* 提取高字节 */
                    *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] & 0xFF);   /* 提取低字节 */
                    iRegIndex++;/* 下一个寄存器 */
                    usNRegs--;  /* 剩余数量减一 */
                }
                break;
                
            case MB_REG_WRITE:
                /* 写入保持寄存器：将两个8位字节组合成16位寄存器数据 */
                while (usNRegs > 0)
                {
                    usRegHoldingBuf[iRegIndex] = (USHORT)(*pucRegBuffer++ << 8);    /* 组合高字节 */
                    usRegHoldingBuf[iRegIndex] |= (USHORT)(*pucRegBuffer++);        /* 组合低字节 */
                    iRegIndex++;/* 下一个寄存器 */
                    usNRegs--;  /* 剩余数量减一 */
                }
                break;
                
            default:
                eStatus = MB_ENOREG;
                break;
        }
    }
    else
    {
        eStatus = MB_ENOREG; /* 寄存器地址超出范围 */
    }

    return eStatus;
}

/**
 * @brief 线圈寄存器回调函数（位读写，功能码01/05/15）
 * @details 线圈(Coils)为1位可读写量，通常用于控制输出（如继电器、指示灯等）。
 *          - 01 读线圈 eMBFuncReadCoils
 *          - 05 写单个线圈 eMBFuncWriteCoil
 *          - 15 写多个线圈 eMBFuncWriteMultipleCoils
 *          协议打包/解包按位进行：主站请求的若干位需按8位为一字节打包到
 *          `pucRegBuffer` 或从中解包恢复到内部线圈缓冲。
 *
 * @param pucRegBuffer 数据缓冲区指针：
 *        - eMode == MB_REG_READ：输出缓冲区，按位打包成字节返回（LSB 对应最低地址位）。
 *        - eMode == MB_REG_WRITE：输入缓冲区，从字节按位解包写入内部线圈状态。
 * @param usAddress    起始线圈地址（位地址）。
 * @param usNCoils     线圈数量（单位：位）。
 * @param eMode        访问模式：MB_REG_READ（读）或 MB_REG_WRITE（写）。
 * @return eMBErrorCode
 *         - MB_ENOERR: 成功
 *         - MB_ENOREG: 地址越界或数量非法
 */
eMBErrorCode
eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode)
{
    eMBErrorCode eStatus = MB_ENOERR;

    /* 检查地址范围是否有效（单位：位/bit） */
    if ((usAddress >= REG_COILS_START) && (usAddress + usNCoils <= REG_COILS_START + REG_COILS_NBITS))
    {
        USHORT usBitOffset = (USHORT)(usAddress - ucRegCoilsStart); /* 计算位偏移：从缓冲区起始位置到目标线圈的位距离 */

        switch (eMode)
        {
            case MB_REG_READ:
                /* 读取线圈：按8位为一组打包输出 */
                while (usNCoils > 0)
                {
                    UCHAR ucNBits = (UCHAR)(usNCoils > 8 ? 8 : usNCoils);
                    *pucRegBuffer++ = xMBUtilGetBits(ucRegCoilsBuf, usBitOffset, ucNBits);
                    usNCoils   -= ucNBits;
                    usBitOffset += ucNBits;
                }
                break;
                
            case MB_REG_WRITE:
                /* 写入线圈：从输入字节按位解包写入内部缓冲 */
                while (usNCoils > 0)
                {
                    UCHAR ucNBits = (UCHAR)(usNCoils > 8 ? 8 : usNCoils);
                    xMBUtilSetBits(ucRegCoilsBuf, usBitOffset, ucNBits, *pucRegBuffer++);
                    usNCoils   -= ucNBits;
                    usBitOffset += ucNBits;
                }
                /* 可在此根据 ucRegCoilsBuf 的变化联动硬件输出（GPIO 等） */
                break;
                
            default:
                eStatus = MB_ENOREG;
                break;
        }
    }
    else
    {
        eStatus = MB_ENOREG; /* 寄存器地址超出范围 */
    }

    return eStatus;
}

/**
 * @brief 离散输入寄存器回调函数（位只读，功能码02）
 * @details 离散输入(Discrete Inputs)为1位只读量，通常用于反映外部开关/传感器
 *          的即时状态。主站仅能读取，不能写入。
 *          - 02 读离散输入 eMBFuncReadDiscreteInputs
 *          读取时应将内部缓存的离散输入状态按位打包为字节写入 `pucRegBuffer`。
 *
 * @param pucRegBuffer 输出缓冲区指针，需将离散输入位按8位一组打包为字节返回
 *                     （LSB 对应最低地址位）。
 * @param usAddress    起始离散输入地址（位地址）。
 * @param usNDiscrete  离散输入数量（单位：位）。
 * @return eMBErrorCode
 *         - MB_ENOERR: 成功
 *         - MB_ENOREG: 地址越界或数量非法
 */
eMBErrorCode
eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
{
    eMBErrorCode eStatus = MB_ENOERR;

    /* 检查地址范围是否有效（单位：位/bit） */
    if ((usAddress >= REG_DISCRETE_START) && (usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_NBITS))
    {
        USHORT usBitOffset = (USHORT)(usAddress - ucRegDiscreteStart); /* 计算位偏移：从缓冲区起始位置到目标线圈的位距离 */

        /* 读取离散输入：按8位为一组打包输出 */
        while (usNDiscrete > 0)
        {
            UCHAR ucNBits = (UCHAR)(usNDiscrete > 8 ? 8 : usNDiscrete);
            *pucRegBuffer++ = xMBUtilGetBits(ucRegDiscreteBuf, usBitOffset, ucNBits);
            usNDiscrete -= ucNBits;
            usBitOffset += ucNBits;
        }
    }
    else
    {
        eStatus = MB_ENOREG; /* 寄存器地址超出范围 */
    }

    return eStatus;
}
